#include "clientconnector.h"

#include "./tools/logic/datahandler.h"
#include "./tools/database/servicemanager.h"
#include "./tools/data/encryptor.h"


ClientConnector::ClientConnector(QObject *parent)
    : QObject{parent}
{
    /* 网络线程和数据库服务线程的交互 */
    // ps: 正常来说消息都应该经过转发线程实现，但是这里是服务于用户登录状态

    // 关于用户下线部分，可能是客户端自主下线，也可能是客户端意外断开连接，需要保证内外存用户状态一致
    // 发送登出信息给Handler
    connect(this, &ClientConnector::logoutUser, &ServiceManager::getInstance(), &ServiceManager::onlyLogoutUser);
    // 通过Service得知某个用户主动下线
    connect(&ServiceManager::getInstance(), &ServiceManager::userIdLogout, this, &ClientConnector::onUserIdLogout);
    // 通过Service层获取UserId（本来只知道套接字描述符，需要解析登录消息才能获取userId）
    connect(&ServiceManager::getInstance(), &ServiceManager::addOnlineUser, this, &ClientConnector::tryAddUserId);


    /* 网络线程和消息转发线程的交互 */

    // 发送套接字描述符和原始数据
    connect(this, &ClientConnector::handleOriginData, &DataHandler::getInstance(), &DataHandler::handleByteData);
    // 接受处理好的数据并发给客户端(这里需要使用重载)
    connect(&DataHandler::getInstance(), QOverload<qintptr, QByteArray>::of(&DataHandler::dataReadyToSend),
            this, &ClientConnector::sendDataByDescriptor);
    connect(&DataHandler::getInstance(), QOverload<QString, QByteArray>::of(&DataHandler::dataReadyToSend),
            this, &ClientConnector::sendDataByUserId);

}

void ClientConnector::InitTcpServer()
{
    server = new QTcpServer(this);  //服务器创建完成
    if(!server->listen(QHostAddress::Any, PORT))
    {
        emit ServerStatusChanged(true);
    }
    else
    {
        emit ServerStatusChanged(false);
    }
    connect(server, &QTcpServer::newConnection, this, &ClientConnector::DealNewConnection);
}

void ClientConnector::DealNewConnection()
{
    qDebug() << "得到新连接";
    QTcpSocket* clientConnection = server->nextPendingConnection();

    // 获取 socket 描述符
    qintptr descriptor = clientConnection->socketDescriptor();
    //将获取的套接字存放到客户端容器中
    m_descriptor2socketMap[descriptor] = clientConnection;
    m_socket2descriptorMap[clientConnection] = descriptor;
    connect(clientConnection, &QTcpSocket::readyRead, this, &ClientConnector::ReadFromClient);
    connect(clientConnection, &QTcpSocket::disconnected, this, &ClientConnector::onClientDisconnected);

}

void ClientConnector::onClientDisconnected()
{
    qDebug() << "有连接断开";
    // 获取当前发送信号的客户端
    QTcpSocket *clientSocket = qobject_cast<QTcpSocket*>(sender());
    qintptr descriptor = m_socket2descriptorMap[clientSocket];
    // qDebug() << descriptor;
    // 只要断开连接就移出套接字管理map（移除双向绑定map）
    m_descriptor2socketMap.remove(descriptor);
    m_socket2descriptorMap.remove(clientSocket);
    // 客户端可能刚登录就断电式结束程序，虚晃服务器一枪，
    // 导致Handler还没发来套接字对应的id（map中没有对应id）就发送信号，所以需要检查
    // 是否需要移出掉tryAddUserId槽函数刚刚添加到map的userId和descriptor
    if (m_descriptor2idMap.contains(descriptor))
    {
        QString userId;
        // qDebug() << "idmap中有UserId";
        userId = m_descriptor2idMap[descriptor];
        // 移出下线的套接字描述符（移除双向绑定map）
        m_descriptor2idMap.remove(descriptor);
        m_id2descriptorMap.remove(userId);
        // qDebug() << userId << "准备下线";
        emit logoutUser(descriptor, userId);
    }
    clientSocket->deleteLater();
}

void ClientConnector::ReadFromClient()
{
    qDebug() << "get package";

    // 获取当前发送信号的客户端
    QTcpSocket *clientSocket = qobject_cast<QTcpSocket*>(sender());
    QByteArray originData = clientSocket->readAll();
    QByteArray decryptedData = Encryptor::decrypt(originData);
    emit handleOriginData(clientSocket->socketDescriptor(), decryptedData);


}

void ClientConnector::sendDataByDescriptor(const qintptr descriptor, const QByteArray responseData)
{
    // 套接字列表里找到对应描述符
    if (!m_descriptor2socketMap.contains(descriptor))
    {
        return;
    }
    // qDebug() << "send package";
    QTcpSocket* clientSocket = m_descriptor2socketMap[descriptor];
    QByteArray encryptedData = Encryptor::encrypt(responseData);
    clientSocket->write(encryptedData);
    clientSocket->flush();
}

void ClientConnector::sendDataByUserId(const QString userId, const QByteArray responseData)
{
    qDebug() << "根据id定向转发";
    // 没有这个用户id对应的描述符就算了（可能是用户断了）
    if (!m_id2descriptorMap.contains(userId))
    {
        qDebug() << userId << "不在线";
        return;
    }
    // 先获取到套接字描述符
    qintptr clientDescriptor = m_id2descriptorMap[userId];
    // 再根据套接字描述符获取到套接字
    QTcpSocket* clientSocket = m_descriptor2socketMap[clientDescriptor];
    QByteArray encryptedData = Encryptor::encrypt(responseData);
    clientSocket->write(encryptedData);
    clientSocket->flush();
    qDebug() << "转发成功";
}

// 添加套接字和UserId之间的映射，之所以说try，是因为狡猾的用户可能上了一下就下线，虚晃服务器
void ClientConnector::tryAddUserId(const qintptr descriptor, const QString userId)
{
    // qDebug() << descriptor << userId << "登录了";
    // clientList中有对应描述符代表当前套接字连接一定是没断开的
    if (m_descriptor2socketMap.contains(descriptor))
    {
        // 添加用户id(这里也许是修改，因为同一个连接可能是不同用户登录)
        m_descriptor2idMap[descriptor] = userId;
        m_id2descriptorMap[userId] = descriptor;
        // qDebug() << "添加新UserId成功";
        // qDebug() << m_clientUserIdMap[descriptor];
    }
    else
    {
        // 如果clientList中没有代表连接已断开（信号传来的时候已经断开了）
        // 同步数据库，让刚刚上线的用户下线
        // qDebug() << "这个狡猾的客户端刚登陆就断开了连接";
        emit logoutUser(descriptor, userId);
    }
}

// Service告知某个用户主动下线，移除相关userId的映射
void ClientConnector::onUserIdLogout(const qintptr descriptor, const QString userId)
{
    // 用户下线但是套接字和套接字描述符不会发生变化，仅仅是修改包含userId的映射
    // 移除id到描述符的映射
    if (m_id2descriptorMap.contains(userId))
    {
        m_id2descriptorMap.remove(userId);
        qDebug() << "m_id2descriptorMap移除用户" << userId << "成功";
    }
    // 移除描述符到id的映射
    if (m_descriptor2idMap.contains(descriptor))
    {
        m_descriptor2idMap.remove(descriptor);
        qDebug() << "m_descriptor2idMap移除成功";
    }
}
